#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/select.h>
#include "Sock.hpp"
#include "log.hpp"

const static int gport = 8000;

#define READ_EVENT (0x1)
#define WRITE_EVENT (0x1 << 1)
#define EXCEPT_EVENT (0x1 << 2)

typedef struct FdEvent
{
    int fd;
    uint8_t event;
    std::string clientip;
    uint16_t clientport;
} type_t;

static const int defaultevent = 0;

class SelectServer
{
    static const int N = (sizeof(fd_set) * 8); // 位图

public:
    SelectServer(uint16_t port = gport)
        : _port(port)
    {
    }

    void InitServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();

        for (int i = 0; i < N; i++)
        {
            // 初始化位图
            _fdarray[i].fd = defaultfd;
            _fdarray[i].event = defaultevent;
            _fdarray[i].clientport = 0;
        }
    }
    void Start()
    {
        // 这里不能直接获取新的连接。因为服务器最开始并没有太多连接。因此新连接到来可以当作是读事件就绪
        _fdarray[0].fd = _listensock.Fd();
        _fdarray[0].event = READ_EVENT;
        while (true)
        {
            fd_set rfds; // 读的套接字集合,是一个输入输出型参数，每次都需要重置
            fd_set wfds;
            FD_ZERO(&rfds);
            FD_ZERO(&wfds);
            int maxfd = _fdarray[0].fd; // 初始化为监听套接字
            for (int i = 0; i < N; i++)
            {
                if (_fdarray[i].fd == defaultfd)
                    continue;

                // 合法fd
                if (_fdarray[i].event & READ_EVENT)
                    FD_SET(_fdarray[i].fd, &rfds); // 将文件描述符fd加入读集合
                if (_fdarray[i].event & WRITE_EVENT)
                    FD_SET(_fdarray[i].fd, &wfds);
                if (maxfd < _fdarray[i].fd)
                    maxfd = _fdarray[i].fd; // 更新最大文件描述符
            }

            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // select监视的文件描述符范围为[0,maxfd]
            switch (n)
            {
            case 0: // 超时
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1: // 失败
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default: // 成功
                logMessage(Debug, "有一个就绪事件发生了: %d", n);
                HandlerEvent(rfds, wfds); // 处理事件
                break;
            }
        }
    }

    void HandlerEvent(fd_set &rfds, fd_set &wfds)
    {
        for (int i = 0; i < N; i++)
        {
            if (_fdarray[i].fd == defaultfd)
                continue;
            if ((_fdarray[i].event & READ_EVENT) && (FD_ISSET(_fdarray[i].fd, &rfds)))
            {
                // 处理读取，1. accept 2. recv

                if (_fdarray[i].fd == _listensock.Fd())
                {
                    Accepter();
                }
                else if (_fdarray[i].fd != _listensock.Fd())
                {
                    Recver(i);
                }
                else
                {
                }
            }
            else if ((_fdarray[i].event & WRITE_EVENT) && (FD_ISSET(_fdarray[i].fd, &wfds)))
            {
                // TODO
            }
            else
            {
            }
        }
    }

    void Accepter() // 在进行的Accepter不会被阻塞
    {
        std::string clientip;
        uint16_t clientport;
        int sock = _listensock.Accept(&clientip, &clientport); // 得到了对应的sock，不代表可以进行read/recv读取sock，因为sock上不一定有数据传输
        // 因此需要将sock交付给select，有select管理
        if (sock < 0)
            return;
        logMessage(Debug, "[%s:%d], sock: %d", clientip.c_str(), clientport, sock);
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (_fdarray[pos].fd == defaultfd)
                break;
        }
        if (pos >= N) // 数组满了，无法容纳新的套接字，即不能建立新连接
        {
            close(sock);
            logMessage(Warning, "sockfd array[] full");
        }
        else
        {
            _fdarray[pos].fd = sock;
            _fdarray[pos].event = READ_EVENT;
            _fdarray[pos].clientip = clientip;
            _fdarray[pos].clientport = clientport;
        }
    }

    void Recver(int index)
    {
        int fd = _fdarray[index].fd;
        char buffer[1024];                                   // 读取缓冲区
        ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 本次读取不会被阻塞，但不保证数据被完整读取
        // 因为buffer是1024，而底层数据可能是2048.因此这里存在一些问题
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << _fdarray[index].clientip << ":" << _fdarray[index].clientport << "# " << buffer << std::endl;

            // 发送回去也要被select管理的,TODO
            std::string echo = buffer;
            echo += " [select server echo]";
            send(fd, echo.c_str(), echo.size(), 0);
        }
        else
        {
            if (s == 0) // 客户端主动关闭连接
                logMessage(Info, "client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
            else // 数据接收错误
                logMessage(Warning, "recv error, client quit ..., fdarray_[i] -> defaultfd: %d->%d", fd, defaultfd);
            close(_fdarray[index].fd);
            _fdarray[index].fd = defaultfd;
            _fdarray[index].event = defaultevent;
            _fdarray[index].clientip.resize(0);
            _fdarray[index].clientport = 0;
        }
    }

    ~SelectServer()
    {
        _listensock.Close();
    }

private:
    uint16_t _port;
    Sock _listensock;
    type_t _fdarray[N]; // 程序管理所有的fd，通过该数组与内核中的fd_set交换信息
};